<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="color-scheme">
    <title>表单校验配置工具</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: #f5f7fa;
            color: #333;
            line-height: 1.6;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }

        header {
            text-align: center;
            margin-bottom: 30px;
            padding: 20px 0;
        }

        h1 {
            color: #2c3e50;
            margin-bottom: 10px;
        }

        .builder-container {
            display: flex;
            gap: 20px;
            flex-wrap: wrap;
        }

        @media (min-width: 768px) {
            .builder-container {
                flex-wrap: nowrap;
            }
        }

        .panel {
            flex: 1;
            min-width: 300px;
            background: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            padding: 20px;
        }

        .panel-title {
            font-size: 1.5rem;
            color: #2c3e50;
            margin-bottom: 20px;
            padding-bottom: 10px;
            border-bottom: 2px solid #3498db;
        }

        .form-group {
            margin-bottom: 15px;
        }

        .form-group label {
            display: block;
            margin-bottom: 5px;
            font-weight: 600;
            color: #2c3e50;
        }

        .form-control {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
        }

        .form-control:focus {
            outline: none;
            border-color: #3498db;
            box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
        }

        .btn {
            display: inline-block;
            padding: 10px 15px;
            background-color: #3498db;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 600;
            transition: background-color 0.3s;
        }

        .btn:hover {
            background-color: #2980b9;
        }

        .btn-danger {
            background-color: #e74c3c;
        }

        .btn-danger:hover {
            background-color: #c0392b;
        }

        .btn-success {
            background-color: #2ecc71;
        }

        .btn-success:hover {
            background-color: #27ae60;
        }

        .field-list {
            margin-top: 20px;
        }

        .field-item {
            background-color: #f8f9fa;
            border: 1px solid #e9ecef;
            border-radius: 6px;
            padding: 15px;
            margin-bottom: 15px;
        }

        .field-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
        }

        .field-name {
            font-weight: 600;
            color: #2c3e50;
        }

        .rules-container {
            margin-top: 15px;
        }

        .rule-item {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            align-items: center;
            background-color: white;
            padding: 10px;
            border-radius: 4px;
            margin-bottom: 10px;
            border: 1px solid #dee2e6;
        }

        .rule-item select,
        .rule-item input {
            flex: 1;
            min-width: 120px;
        }

        .remove-rule-btn {
            background: none;
            border: none;
            color: #e74c3c;
            font-size: 18px;
            cursor: pointer;
            padding: 0;
            width: 30px;
            height: 30px;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .remove-rule-btn:hover {
            background-color: rgba(231, 76, 60, 0.1);
            border-radius: 50%;
        }

        .code-output {
            background-color: #1e1e1e;
            color: #d4d4d4;
            border-radius: 6px;
            padding: 20px;
            font-family: 'Courier New', monospace;
            font-size: 14px;
            height: 500px;
            overflow: auto;
            margin-bottom: 20px;
            white-space: pre-wrap;
        }

        .actions {
            display: flex;
            gap: 10px;
        }

        .notification {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 15px 20px;
            background-color: #2ecc71;
            color: white;
            border-radius: 4px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
            transform: translateX(200%);
            transition: transform 0.3s ease;
            z-index: 1000;
        }

        .notification.show {
            transform: translateX(0);
        }
    </style>
</head>
<body>
<div class="container">
    <header>
        <h1>表单校验配置工具</h1>
        <p>左侧配置校验规则，右侧实时生成代码</p>
    </header>

    <div class="builder-container">
        <!-- 左侧可视化操作区域 -->
        <div class="panel">
            <h2 class="panel-title">可视化配置</h2>

            <!-- 添加字段 -->
            <div class="field-config">
                <h3>添加字段</h3>
                <div class="form-group">
                    <label for="fieldName">字段名称:</label>
                    <input type="text" id="fieldName" class="form-control" placeholder="例如: username">
                </div>
                <div class="form-group">
                    <label for="fieldLabel">显示名称:</label>
                    <input type="text" id="fieldLabel" class="form-control" placeholder="例如: 用户名">
                </div>
                <button id="addFieldBtn" class="btn">添加字段</button>
            </div>

            <!-- 字段列表 -->
            <div class="field-list" id="fieldsContainer">
                <!-- 字段将在这里动态添加 -->
            </div>
        </div>

        <!-- 右侧代码生成区域 -->
        <div class="panel">
            <h2 class="panel-title">生成的代码</h2>
            <div class="code-output" id="codeOutput">
                // 请添加字段和校验规则
            </div>
            <div class="actions">
                <button id="copyCodeBtn" class="btn">复制代码</button>
                <button id="downloadCodeBtn" class="btn btn-success">下载代码</button>
            </div>
        </div>
    </div>
</div>

<div class="notification" id="notification">代码已复制到剪贴板</div>

<script>
    // 全局变量
    let validationConfig = {
        fields: []
    };

    // DOM 元素
    const elements = {
        fieldName: document.getElementById('fieldName'),
        fieldLabel: document.getElementById('fieldLabel'),
        addFieldBtn: document.getElementById('addFieldBtn'),
        fieldsContainer: document.getElementById('fieldsContainer'),
        codeOutput: document.getElementById('codeOutput'),
        copyCodeBtn: document.getElementById('copyCodeBtn'),
        downloadCodeBtn: document.getElementById('downloadCodeBtn'),
        notification: document.getElementById('notification')
    };

    // 事件监听器
    elements.addFieldBtn.addEventListener('click', addField);
    elements.copyCodeBtn.addEventListener('click', copyCode);
    elements.downloadCodeBtn.addEventListener('click', downloadCode);

    // 添加字段
    function addField() {
        const name = elements.fieldName.value.trim();
        const label = elements.fieldLabel.value.trim();

        if (!name || !label) {
            alert('请填写字段名称和显示名称');
            return;
        }

        // 检查字段是否已存在
        if (validationConfig.fields.some(field => field.name === name)) {
            alert('字段名称已存在');
            return;
        }

        // 添加到配置中
        validationConfig.fields.push({
            name: name,
            label: label,
            rules: []
        });

        // 清空输入框
        elements.fieldName.value = '';
        elements.fieldLabel.value = '';

        // 更新UI
        renderFields();
        generateCode();
    }

    // 删除字段
    function removeField(index) {
        validationConfig.fields.splice(index, 1);
        renderFields();
        generateCode();
    }

    // 添加规则
    function addRule(fieldIndex) {
        validationConfig.fields[fieldIndex].rules.push({
            type: 'required',
            message: ''
        });
        renderFields();
        generateCode();
    }

    // 删除规则
    function removeRule(fieldIndex, ruleIndex) {
        validationConfig.fields[fieldIndex].rules.splice(ruleIndex, 1);
        renderFields();
        generateCode();
    }

    // 更新规则属性
    function updateRule(fieldIndex, ruleIndex, property, value) {
        validationConfig.fields[fieldIndex].rules[ruleIndex][property] = value;
        generateCode();
    }

    // 渲染字段列表
    function renderFields() {
        elements.fieldsContainer.innerHTML = '';

        if (validationConfig.fields.length === 0) {
            elements.fieldsContainer.innerHTML = '<p>暂无字段，请添加字段</p>';
            return;
        }

        validationConfig.fields.forEach((field, fieldIndex) => {
            const fieldElement = document.createElement('div');
            fieldElement.className = 'field-item';
            fieldElement.innerHTML = `
                    <div class="field-header">
                        <span class="field-name">${field.label} (${field.name})</span>
                        <button class="btn btn-danger" onclick="removeField(${fieldIndex})">删除</button>
                    </div>
                    <div class="rules-container">
                        <h4>校验规则</h4>
                        <div id="rules-${fieldIndex}">
                            <!-- 规则将在这里动态添加 -->
                        </div>
                        <button class="btn" onclick="addRule(${fieldIndex})">添加规则</button>
                    </div>
                `;

            elements.fieldsContainer.appendChild(fieldElement);

            // 渲染规则
            const rulesContainer = document.getElementById(`rules-${fieldIndex}`);
            field.rules.forEach((rule, ruleIndex) => {
                const ruleElement = document.createElement('div');
                ruleElement.className = 'rule-item';
                ruleElement.innerHTML = `
                        <select class="form-control rule-type" data-field="${fieldIndex}" data-rule="${ruleIndex}">
                            <option value="required" ${rule.type === 'required' ? 'selected' : ''}>必填</option>
                            <option value="minLength" ${rule.type === 'minLength' ? 'selected' : ''}>最小长度</option>
                            <option value="maxLength" ${rule.type === 'maxLength' ? 'selected' : ''}>最大长度</option>
                            <option value="pattern" ${rule.type === 'pattern' ? 'selected' : ''}>正则表达式</option>
                            <option value="email" ${rule.type === 'email' ? 'selected' : ''}>邮箱格式</option>
                            <option value="number" ${rule.type === 'number' ? 'selected' : ''}>数字格式</option>
                        </select>

                        ${getRuleValueInput(rule, fieldIndex, ruleIndex)}

                        <input type="text" class="form-control rule-message"
                               placeholder="错误信息"
                               value="${rule.message}"
                               data-field="${fieldIndex}"
                               data-rule="${ruleIndex}">

                        <button class="remove-rule-btn" onclick="removeRule(${fieldIndex}, ${ruleIndex})">×</button>
                    `;

                rulesContainer.appendChild(ruleElement);
            });
        });

        // 绑定规则输入事件
        bindRuleEvents();
    }

    // 获取规则值输入框
    function getRuleValueInput(rule, fieldIndex, ruleIndex) {
        if (rule.type === 'minLength' || rule.type === 'maxLength') {
            return `<input type="number" class="form-control rule-value"
                              placeholder="长度值"
                              value="${rule.value || ''}"
                              data-field="${fieldIndex}"
                              data-rule="${ruleIndex}">`;
        }

        if (rule.type === 'pattern') {
            return `<input type="text" class="form-control rule-value"
                              placeholder="正则表达式"
                              value="${rule.value || ''}"
                              data-field="${fieldIndex}"
                              data-rule="${ruleIndex}">`;
        }

        return '';
    }

    // 绑定规则事件
    function bindRuleEvents() {
        // 绑定规则类型变化事件
        document.querySelectorAll('.rule-type').forEach(select => {
            select.addEventListener('change', function () {
                const fieldIndex = parseInt(this.dataset.field);
                const ruleIndex = parseInt(this.dataset.rule);
                const newType = this.value;

                // 更新规则类型
                updateRule(fieldIndex, ruleIndex, 'type', newType);

                // 重新渲染字段以更新值输入框
                renderFields();
                generateCode();
            });
        });

        // 绑定规则值变化事件
        document.querySelectorAll('.rule-value').forEach(input => {
            input.addEventListener('input', function () {
                const fieldIndex = parseInt(this.dataset.field);
                const ruleIndex = parseInt(this.dataset.rule);
                updateRule(fieldIndex, ruleIndex, 'value', this.value);
            });
        });

        // 绑定错误信息变化事件
        document.querySelectorAll('.rule-message').forEach(input => {
            input.addEventListener('input', function () {
                const fieldIndex = parseInt(this.dataset.field);
                const ruleIndex = parseInt(this.dataset.rule);
                updateRule(fieldIndex, ruleIndex, 'message', this.value);
            });
        });
    }

    // 生成校验代码
    function generateCode() {
        if (validationConfig.fields.length === 0) {
            elements.codeOutput.textContent = '// 请添加字段和校验规则';
            return;
        }

        let code = `// 表单校验配置\nconst validationRules = {\n`;

        validationConfig.fields.forEach(field => {
            code += `  ${field.name}: [\n`;

            field.rules.forEach(rule => {
                code += `    {\n`;
                code += `      type: '${rule.type}',\n`;

                if (rule.value !== undefined && rule.value !== '') {
                    if (rule.type === 'pattern') {
                        code += `      value: /${rule.value}/,\n`;
                    } else {
                        code += `      value: ${rule.value},\n`;
                    }
                }

                code += `      message: '${rule.message || getDefaultMessage(field.label, rule.type, rule.value)}'\n`;
                code += `    },\n`;
            });

            code += `  ],\n`;
        });

        code += `};\n\n`;

        // 添加校验函数
        code += `// 校验函数\nfunction validateField(fieldName, value) {\n`;
        code += `  const rules = validationRules[fieldName];\n`;
        code += `  if (!rules) return [];\n\n`;
        code += `  const errors = [];\n`;
        code += `  for (const rule of rules) {\n`;
        code += `    switch (rule.type) {\n`;
        code += `      case 'required':\n`;
        code += `        if (!value || value.trim() === '') {\n`;
        code += `          errors.push(rule.message);\n`;
        code += `        }\n`;
        code += `        break;\n`;
        code += `      case 'minLength':\n`;
        code += `        if (value.length < rule.value) {\n`;
        code += `          errors.push(rule.message);\n`;
        code += `        }\n`;
        code += `        break;\n`;
        code += `      case 'maxLength':\n`;
        code += `        if (value.length > rule.value) {\n`;
        code += `          errors.push(rule.message);\n`;
        code += `        }\n`;
        code += `        break;\n`;
        code += `      case 'pattern':\n`;
        code += `        if (!rule.value.test(value)) {\n`;
        code += `          errors.push(rule.message);\n`;
        code += `        }\n`;
        code += `        break;\n`;
        code += `      case 'email':\n`;
        code += `        const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n`;
        code += `        if (!emailRegex.test(value)) {\n`;
        code += `          errors.push(rule.message);\n`;
        code += `        }\n`;
        code += `        break;\n`;
        code += `      case 'number':\n`;
        code += `        if (isNaN(value)) {\n`;
        code += `          errors.push(rule.message);\n`;
        code += `        }\n`;
        code += `        break;\n`;
        code += `    }\n`;
        code += `  }\n`;
        code += `  return errors;\n`;
        code += `}\n\n`;

        // 添加整体表单校验函数
        code += `// 表单校验函数\nfunction validateForm(formData) {\n`;
        code += `  const errors = {};\n`;
        code += `  Object.keys(validationRules).forEach(fieldName => {\n`;
        code += `    const fieldErrors = validateField(fieldName, formData[fieldName]);\n`;
        code += `    if (fieldErrors.length > 0) {\n`;
        code += `      errors[fieldName] = fieldErrors;\n`;
        code += `    }\n`;
        code += `  });\n`;
        code += `  return errors;\n`;
        code += `}`;

        elements.codeOutput.textContent = code;
    }

    // 获取默认错误信息
    function getDefaultMessage(label, type, value) {
        switch (type) {
            case 'required':
                return `${label}不能为空`;
            case 'minLength':
                return `${label}长度不能少于${value}个字符`;
            case 'maxLength':
                return `${label}长度不能超过${value}个字符`;
            case 'pattern':
                return `${label}格式不正确`;
            case 'email':
                return `${label}必须是有效的邮箱地址`;
            case 'number':
                return `${label}必须是数字`;
            default:
                return `${label}校验失败`;
        }
    }

    // 复制代码到剪贴板
    function copyCode() {
        const textarea = document.createElement('textarea');
        textarea.value = elements.codeOutput.textContent;
        document.body.appendChild(textarea);
        textarea.select();
        document.execCommand('copy');
        document.body.removeChild(textarea);

        // 显示通知
        elements.notification.classList.add('show');
        setTimeout(() => {
            elements.notification.classList.remove('show');
        }, 2000);
    }

    // 下载代码文件
    function downloadCode() {
        const code = elements.codeOutput.textContent;
        const blob = new Blob([code], {type: 'text/javascript'});
        const url = URL.createObjectURL(blob);

        const a = document.createElement('a');
        a.href = url;
        a.download = 'validation-config.js';
        document.body.appendChild(a);
        a.click();

        setTimeout(() => {
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        }, 100);
    }

    // 初始化
    renderFields();
    generateCode();
</script>
</body>
</html>
